package utils

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/mmcloughlin/geohash"
	"github.com/mozillazg/go-pinyin"
	"io"
	"math"
	"reflect"
	"strconv"
	"strings"
)

var hex2binary map[byte]byte = map[byte]byte{
	48: 0,
	49: 1,
	50: 2,
	51: 3,
	52: 4,
	53: 5,
	54: 6,
	55: 7,
	56: 8,
	57: 9,

	65: 10,
	66: 11,
	67: 12,
	68: 13,
	69: 14,
	70: 15,

	97:  10,
	98:  11,
	99:  12,
	100: 13,
	101: 14,
	102: 15,
}

func ParseVersion(str string, v uint) uint {
	array := strings.Split(str, ".")
	if len(array) > 0 {
		v = 0
		for _, vs := range array {
			v = v<<8 | uint(ParseInteger(vs, 0)&0xff)
		}
	}
	return v
}

func FormatVersion(v uint) string {
	return fmt.Sprintf("%d.%d.%d.%d",
		v>>24&0xff,
		v>>16&0xff,
		v>>8&0xff,
		v&0xff)
}

func FormatBytes(src interface{}) (dst []byte) {
	reflect.Indirect(reflect.ValueOf(src))
	return
}

func ParseJSON(src interface{}, dst interface{}) (err error) {
	var buffer []byte
	switch r := src.(type) {
	case []byte:
		buffer = r
	case string:
		buffer = []byte(r)
	case io.Reader:
		return json.NewDecoder(r).Decode(dst)
	default:
		buffer, _ = json.Marshal(src)
	}
	if nil == buffer {
		err = errors.New("is not conversion byte array")
	} else {
		err = json.Unmarshal(buffer, dst)
	}
	return
}

func FormatJSON(v interface{}, args ...bool) string {
	var buf []byte
	var err error
	if len(args) > 0 && args[0] {
		buf, err = json.MarshalIndent(v, "", "    ")
	} else {
		buf, err = json.Marshal(v)
	}
	if nil != err {
		println("FormatJSON:", err.Error())
	}
	return string(buf)
}

func FormatIntegerArray(arr []int64, pos int, args ...string) string {
	var buf bytes.Buffer

	for _, v := range args[:pos] {
		buf.WriteString(v)
	}

	for _, v := range arr {
		buf.WriteString(FormatInteger(v))
		buf.WriteByte(0x2C)
	}
	buf.Truncate(buf.Len() - 1)

	for _, v := range args[pos:] {
		buf.WriteString(v)
	}
	return buf.String()
}

func FormatInteger(v int64) string {
	if 0 == v {
		return "0"
	}
	var buffer [24]byte
	var pos = len(buffer)
	for pos > 0 && 0 != v {
		i := v / 10
		pos--
		buffer[pos] = byte(v - i*10 + 48)
		v = i
	}
	return string(buffer[pos:])
}

func FirstInteger(s string, v int64) int64 {
	for i, c := range s {
		if uint(c)-48 < uint(10) {
			return ParseInteger(s[i:], v)
		}
	}
	return v
}

func ParseInteger(s string, v int64) int64 {
	if len(s) > 0 && uint(s[0])-48 < 10 {
		v = 0
		for _, c := range s {
			u := uint(c) - 48
			if u > uint(9) {
				break
			}
			v = v*10 + int64(u)
		}
	}
	return v
}

func ParseBoolean(src interface{}, v bool) bool {
	if src == nil {
		return v
	}
	src_v := reflect.Indirect(reflect.ValueOf(src))
	switch reflect.TypeOf(src).Kind() {
	case reflect.Ptr:
		v = src_v.IsNil()
	case reflect.Bool:
		v = src_v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		v = src_v.Int() != 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		v = src_v.Uint() != 0
	case reflect.String:
		if b, err := strconv.ParseBool(src_v.String()); err == nil {
			v = b
		}
	case reflect.Array, reflect.Slice, reflect.Map, reflect.Chan:
		v = src_v.Len() > 0
	}
	return v
}

func HexDecoder(src []byte, dst ...[]byte) (buf []byte) {
	src_len := len(src) - 1
	dst_len := len(src) >> 1
	if src_len < 1 {
		return nil
	}

	if len(dst) > 0 && nil != dst {
		buf = dst[0]
	} else {
		buf = make([]byte, 0, dst_len)
	}

	for i := 0; i < dst_len; i++ {
		h := hex2binary[src[i*2]]
		l := hex2binary[src[i*2+1]]
		buf = append(buf, byte((h<<4)|l))
	}

	return buf
}

func HexEncoder(src []byte, args ...interface{}) string {
	var table string = "0123456789ABCDEF"
	var pfx []byte
	var dir []byte
	var dst []byte
	switch len(args) {
	case 3:
		dir = []byte(args[2].(string))
		fallthrough
	case 2:
		pfx = []byte(args[1].(string))
		fallthrough
	case 1:
		if !args[0].(bool) {
			table = "0123456789abcdef"
		}
	}
	if n := len(dir) + len(pfx); n > 0 {
		n = (2 + n) * len(src)
		dst = make([]byte, 0, n)
		for _, v := range src {
			dst = append(dst, pfx...)
			dst = append(dst, table[v>>4])
			dst = append(dst, table[v&0x0f])
			dst = append(dst, dir...)
		}
		dst = dst[:n-len(dir)]
	} else {
		dst = dst[:n-len(dir)]
		dst = make([]byte, len(src)*2)
		for i, v := range src {
			dst[i*2] = table[v>>4]
			dst[i*2+1] = table[v&0x0f]
		}
	}
	return string(dst)
}

func URLEncoder(src string) string {
	bitmap := [4]uint64{
		0x3ff640100000000,
		0x7fffffe87fffffe, 0, 0}
	dst := make([]byte, 0)
	table := [16]byte{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}
	for _, v := range []byte(src) {
		if 0 != bitmap[v>>6]>>(v&0x3f)&1 {
			if v == 0x20 {
				v = 0x2b
			}
			dst = append(dst, v)
		} else {
			dst = append(dst, 0x25)
			dst = append(dst, table[v>>4])
			dst = append(dst, table[v&0xf])
		}
	}
	return string(dst)
}

func URLJoin(item ...string) string {
	buffer := bytes.NewBufferString("")
	var has bool = true
	for _, v := range item {
		if "" == v {
			continue
		}
		if has == (47 == v[0]) {
			if has {
				v = v[1:]
			} else {
				buffer.WriteByte(47)
			}
		}
		buffer.WriteString(v)
		has = 47 == v[len(v)-1]
	}
	return buffer.String()
}

func SafeInteger(s interface{}, v int64) int64 {
	if nil == s {
		return v
	}
	switch r := s.(type) {
	case string:
		v = ParseInteger(r, v)
	case json.Number:
		v = ParseInteger(string(r), v)
	case []byte:
		v = ParseInteger(string(r), v)
	case float64:
		v = int64(r)
	case float32:
		v = int64(r)
	case uint:
		v = int64(r)
	case int:
		v = int64(r)
	case uint64:
		v = int64(r)
	case int64:
		v = int64(r)
	case uint32:
		v = int64(r)
	case int32:
		v = int64(r)
	case uint16:
		v = int64(r)
	case int16:
		v = int64(r)
	case byte:
		v = int64(r)
	}
	return v
}

func Hash(location string) string {
	if val := strings.Split(location, ","); len(val) > 0 {
		lat, _ := strconv.ParseFloat(val[0], 64)
		lng, _ := strconv.ParseFloat(val[1], 64)
		return geohash.Encode(lat, lng)
	}
	return ""
}

func Pinyin(name string) string {
	arr := pinyin.Pinyin(name, pinyin.NewArgs())
	if len(arr) <= 0 {
		return ""
	}
	var py strings.Builder
	for _, v := range arr {
		b := []byte(v[0])
		b[0] -= 32
		py.Write(b)
	}
	return py.String()
}

func Distance(lat1, lon1, lat2, lon2 float64) float64 {
	var la1, lo1, la2, lo2, r float64
	la1 = lat1 * math.Pi / 180
	lo1 = lon1 * math.Pi / 180
	la2 = lat2 * math.Pi / 180
	lo2 = lon2 * math.Pi / 180

	r = 6378100 // Earth radius in METERS

	// calculate
	h := __hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*__hsin(lo2-lo1)

	return 2 * r * math.Asin(math.Sqrt(h))
}

func __hsin(theta float64) float64 {
	return math.Pow(math.Sin(theta/2), 2)
}

func MaskString(str string, begin int, end int, mark byte) string {
	result := []byte(str)
	for i := begin - 1; i <= end-1 && i < len(str) && i >= 0; i++ {
		result[i] = mark
	}
	return string(result[:])
}
